#pragma once

#include <tuple>

#include "utility.hpp"
#include "type_traits.hpp"
namespace cpp
{
	namespace __tuple
	{
		template <class F, class Tuple, std::size_t... I>
		constexpr inline decltype(auto) _ApplyImpl(F&& f, Tuple&& t, std::index_sequence<I...>)
		{
			return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
		}
	}

	template <class F, class Tuple>
	constexpr inline decltype(auto) apply(F&& f, Tuple&& t)
	{
		return __tuple::_ApplyImpl(std::forward<F>(f), std::forward<Tuple>(t),
								   std::make_index_sequence <std::tuple_size<std::decay_t<Tuple> >::value > {});
	}

	namespace __tuple
	{
		template <class T, class Tuple, std::size_t... I>
		constexpr T _MakeFromTupleImpl(Tuple&& t, std::index_sequence<I...>)
		{
			return T(std::get<I>(std::forward<Tuple>(t))...);
		}
	}

	template <class T, class Tuple>
	constexpr T make_from_tuple(Tuple&& t)
	{
		return __tuple::_MakeFromTupleImpl<T>(std::forward<Tuple>(t),
											  std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
	}

	namespace __functional
	{
		template<size_t Size = sizeof(void*) * 2>
		struct _SooTrait
		{
			using  _StorageType = std::aligned_union<Size, std::max_align_t>;
			using storage_t = typename _StorageType::type;

			template<
				class T,
				size_t AlignFactor = _StorageType::alignment_value / alignof(T),
				class = std::enable_if_t< (sizeof(T) <= sizeof(storage_t)) && ((AlignFactor != 0) && !(AlignFactor & (AlignFactor - 1))) >,
				class = std::enable_if_t< std::is_nothrow_move_constructible<T>::value >
			>
				static  std::true_type test(int);

			template<class T>
			static std::false_type test(...);

			// test is public
			template<class T>
			using can_fit = decltype(test<T>(0));

			using storage_t = typename _StorageType::type;
		};
	}


	template<class Fn>
	class function;

	namespace __functional
	{
		template<class _Trait, std::size_t ...I>
		auto _ArgsHelper2(cpp::index_sequence<I...>)->cpp::pack<typename _Trait::template arg_type_t<I>... >;

		template<class _Trait>
		auto _ArgsHelper(int) -> decltype(_ArgsHelper2<_Trait>(cpp::make_index_sequence<_Trait::arity>()));

		template<class _Trait>
		void _ArgsHelper(long);


		template<class Derived, class _Trait, class Pack>
		class _Function;

		template<class Derived, class _Trait, class... Args>
		class _Function <Derived, _Trait, cpp::pack<Args...> >
		{
		protected:
		public:
			using return_type = typename _Trait::result_type;

			//
			using  SooTrait = __functional::_SooTrait<>;
			using  storage_t = typename SooTrait::storage_t;

			union Data
			{
				void*						_dynamic = nullptr;
				storage_t					_stack;
			};

			struct vtable
			{
				const std::type_info &	(*_type)()		noexcept;
				void(*_delete)(Data &)				noexcept;
				void(*_copy)(const Data & from, Data & to);
				void(*_move)(Data & from, Data & to)		noexcept;
				return_type(*_call)(Data &, Args&&...);
				return_type(*_callConst)(const Data &, Args&&...);
			};

			template<class T, class = void >
			struct stack_vtable
			{
				static const std::type_info& type() noexcept
				{
					return typeid(T);
				}

				static void copy(const Data & from, Data & to)
				{
					::new (static_cast<void*>(&to._stack)) T(reinterpret_cast<const T&>(from._stack));
				}

				static void move(Data &from, Data & to) noexcept
				{
					::new (static_cast<void*>(&to._stack)) T(std::move(reinterpret_cast<T &>(from._stack)));
					_delete(from);
				}

				template<class T>
				static inline void _Destory(T*p){ (void)p;  p->~T(); }

				static void _delete(Data & data) noexcept
				{
					_Destory(reinterpret_cast<T*>(&data._stack));
				}

				template<class...Args>
				static void create(Data & data, Args&&...args)
				{
					::new (static_cast<void*>(&data._stack))  T(std::forward<Args>(args)...);
				}

				static return_type call(Data & data, Args&&... args)
				{
					return (return_type)std::invoke(reinterpret_cast<T&>(data._stack), std::forward<Args>(args)...);
				}

				static return_type callConst(const Data & data, Args&&... args)
				{
					return (return_type)std::invoke(reinterpret_cast<const T&>(data._stack), std::forward<Args>(args)...);
				}

				static vtable* get_vtable()
				{
					static vtable _vtable = {
						type,
						_delete,
						copy,
						move,
						call,
						callConst
					};
					return &_vtable;
				}
			};

			template<class T, class = void>
			struct dynamic_vtable
			{
				static const std::type_info& type() noexcept
				{
					return typeid(T);
				}
				static void copy(const Data & from, Data & to)
				{
					to._dynamic = new T(*static_cast<const T *>(from._dynamic));
				}

				static void move(Data & from, Data & to) noexcept
				{
					to._dynamic = from._dynamic;
					from._dynamic = nullptr;
				}

				static void _delete(Data & data) noexcept
				{
					delete static_cast<T*>(data._dynamic);
				}

				template<class...Args>
				static void create(Data & data, Args&&...args)
				{
					data._dynamic = new T(std::forward<Args>(args)...);
				}

				static return_type call(Data & data, Args&&... args)
				{
					return (return_type)std::invoke(*reinterpret_cast<T*>(data._dynamic), std::forward<Args>(args)...);
				}

				static return_type callConst(const Data & data, Args&&... args)
				{
					return  (return_type)std::invoke(*reinterpret_cast<const T*>(data._dynamic), std::forward<Args>(args)...);
				}

				static vtable* get_vtable()
				{
					static vtable _vtable = {
						type,
						_delete,
						copy,
						move,
						call,
						callConst
					};
					return &_vtable;
				}
			};

			return_type operator()(Args... args)
			{
				return static_cast<Derived&>(*this)._Call(std::forward<Args>(args)...);
			}

			return_type operator()(Args... args) const
			{
				return static_cast<const Derived&>(*this)._Call(std::forward<Args>(args)...);
			}

		protected:
			template<class F>
			using _Callable = std::is_invocable_r< return_type, F, Args...>;// cpp::is_callable<F(Args...), return_type>;
		};


		template <class _Fp>
		inline	bool __not_null(_Fp const&) { return true; }

		template <class _Fp>
		inline	bool __not_null(_Fp* __ptr) { return __ptr != nullptr; }

		template <class _Ret, class _Class>
		inline	bool __not_null(_Ret _Class::*__ptr) { return __ptr; }

		template <class _Fp>
		inline	bool __not_null(function<_Fp> const& __f) { return !!__f; }

	}

	template<class Fn>
	class function :private __functional::_Function<
		function<Fn>,
		cpp::callable_traits<Fn>,
		decltype(__functional::_ArgsHelper<cpp::callable_traits<Fn>>(0))>
	{
		using base_t = __functional::_Function<
			function<Fn>,
			cpp::callable_traits<Fn>,
			decltype(__functional::_ArgsHelper<cpp::callable_traits<Fn>>(0))>;

		friend base_t;
	public:
		using return_type = typename base_t::return_type;
	private:
		typename base_t::Data		_data;
		typename base_t::vtable *	pvtable = nullptr;

		template<class T>
		using can_fit = typename base_t::SooTrait::template can_fit<T>;

		template<class T>
		using VTable = std::conditional_t< can_fit<T>::value,
			typename base_t::template stack_vtable<T>,
			typename base_t::template dynamic_vtable<T> >;

	public:

		function() noexcept{}

		function(std::nullptr_t) noexcept{}

		function(const function& other)
			:pvtable(other.pvtable)
		{
			if(other)
			{
				other.pvtable->_copy(other._data, _data);
			}
		}

		function(function&& other)
			:pvtable(other.pvtable)
		{
			if(other)
			{
				other.pvtable->_move(other._data, _data);
				other.pvtable = nullptr;
			}
		}

		template< class F, class = std::enable_if_t < base_t::template _Callable<F>::value  >>
		function(F f)
			:pvtable(nullptr)
		{
			if(__functional::__not_null(f))
			{
				pvtable = VTable<F>::get_vtable();
				VTable<F>::create(_data, std::move(f));
			}
		}

		~function()noexcept
		{
			reset();
		}

		function& operator=(const function& other)
		{
			if(this == &other)
			{
				return *this;
			}
			function(other).swap(*this);
			return *this;
		}

		function& operator=(function&& other)
		{
			if(this == &other)
			{
				return *this;
			}
			function(std::move(other)).swap(*this);
			return *this;
		}

		function& operator=(std::nullptr_t) noexcept
		{
			reset();
			return *this;
		}

		template< class F, class = std::enable_if_t < base_t::template _Callable<F>::value  > >
		function& operator=(F&& f)
		{
			function(std::forward<F>(f)).swap(*this);
			return *this;
		}

		template< class F >
		function& operator=(std::reference_wrapper<F> f) noexcept
		{
			function(f).swap(*this);
			return *this;
		}

		void swap(function& other) noexcept
		{
			if(!has_value() && !other.has_value())
				return;

			if(has_value() && other.has_value())
			{
				if(this == &other)
				{
					return;
				}

				function tmp;
				tmp.pvtable = pvtable;
				pvtable->_move(_data, tmp._data);

				pvtable = other.pvtable;
				pvtable->_move(other._data, _data);

				other.pvtable = tmp.pvtable;
				other.pvtable->_move(tmp._data, other._data);
				tmp.pvtable = nullptr;
			}
			else
			{
				function* hasValue = has_value() ? this : &other;
				function* noValue = !has_value() ? this : &other;

				noValue->pvtable = hasValue->pvtable;
				hasValue->pvtable->_move(hasValue->_data, noValue->_data);
				hasValue->pvtable = nullptr;
			}
		}

		explicit operator bool() const noexcept
		{
			return has_value();
		}

		const std::type_info& target_type() const noexcept
		{
			return pvtable ? pvtable->_type() : typeid(void);
		}

		template< class T >
		T* target() noexcept
		{
			if(pvtable && pvtable->_type() == typeid(T))
			{
				if(can_fit< T >::value)
				{
					return std::addressof(reinterpret_cast<T&>(_data._stack));
				}
				else
				{
					return static_cast<T*>(_data._dynamic);
				}
			}
			return nullptr;
		}

		template< class T >
		const T* target() const noexcept
		{
			if(pvtable && pvtable->_type() == typeid(T))
			{
				if(can_fit< T >::value)
				{
					return std::addressof(reinterpret_cast<const T&>(_data._stack));
				}
				else
				{
					return static_cast<const T*>(_data._dynamic);
				}
			}
			return nullptr;
		}


		using base_t::operator ();
	private:
		void reset() noexcept
		{
			if(pvtable)
			{
				pvtable->_delete(_data);
				pvtable = nullptr;
			}
		}

		bool has_value() const noexcept
		{
			return pvtable != nullptr;
		}


		template<class... _Args>
		return_type _Call(_Args&&... args)
		{
			if(!pvtable)
			{
				throw std::bad_function_call();
			}
			return	pvtable->_call(_data, std::forward<_Args>(args)...);
		}

		template<class... _Args>
		return_type _Call(_Args&&... args) const
		{
			if(!pvtable)
			{
				throw std::bad_function_call();
			}
			return	pvtable->_callConst(_data, std::forward<_Args>(args)...);
		}

	};

	template< class R, class... ArgTypes >
	bool operator==(const std::function<R(ArgTypes...)>& f, std::nullptr_t) noexcept
	{
		return !f;
	}

	template< class R, class... ArgTypes >
	bool operator==(std::nullptr_t, const std::function<R(ArgTypes...)>& f) noexcept
	{
		return !f;
	}

	template< class R, class... ArgTypes >
	bool operator!=(const std::function<R(ArgTypes...)>& f, std::nullptr_t) noexcept
	{
		return (bool)f;
	}

	template< class R, class... ArgTypes >
	bool operator!=(std::nullptr_t, const std::function<R(ArgTypes...)>& f) noexcept
	{
		return (bool)f;
	}


	template< class R, class... Args >
	void swap(function<R(Args...)> &lhs, function<R(Args...)> &rhs) noexcept
	{
		lhs.swap(rhs);
	}
}